﻿using Microsoft.Extensions.Caching.Memory;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Apex.Commons;
using Apex.Commons.Extentions;

namespace Apex.ASPNETCore
{
    /// <summary>
    /// 用ASP.NET的IMemoryCache实现的内存缓存帮助器。
    /// </summary>
    public class MemoryCacheHelper : IMemoryCacheHelper
    {
        private readonly IMemoryCache memoryCache;

        /// <summary>
        /// 初始化 <see cref="MemoryCacheHelper"/> 类的新实例。
        /// </summary>
        /// <param name="memoryCache">用于缓存数据的内存缓存实例。</param>
        public MemoryCacheHelper(IMemoryCache memoryCache)
        {
            this.memoryCache = memoryCache;
        }

        /// <summary>
        /// 缓存值的类型校验：因为IEnumerable、IQueryable等有延迟执行的问题，可能会引起意外行为，因此禁止使用这些类型。
        /// </summary>
        /// <typeparam name="TResult">缓存的值的类型。</typeparam>
        /// <exception cref="InvalidOperationException">当检测到缓存的值类型为IEnumerable、IQueryable或相关类型时，抛出此异常。</exception>
        private static void ValidateValueType<TResult>()
        {
            // 缓存值的类型
            Type typeResult = typeof(TResult);

            // 如果是IEnumerable<String>这样的泛型类型，则把String这样的具体类型信息去掉，再比较
            if (typeResult.IsGenericType)
            {
                typeResult = typeResult.GetGenericTypeDefinition();
            }
            // 注意用相等比较，不要用IsAssignableTo
            if (typeResult == typeof(IEnumerable<>) || typeResult == typeof(IEnumerable)
                || typeResult == typeof(IAsyncEnumerable<TResult>)
                || typeResult == typeof(IQueryable<TResult>) || typeResult == typeof(IQueryable))
            {
                throw new InvalidOperationException($"TResult of {typeResult} is not allowed, please use List<T> or T[] instead.");
            }
        }

        /// <summary>
        /// 初始化缓存项的绝对过期时间
        /// </summary>
        /// <param name="entry">缓存项</param>
        /// <param name="baseExpireSeconds">基础过期时间（秒）</param>
        private static void InitCacheEntry(ICacheEntry entry, int baseExpireSeconds)
        {
            //过期时间.Random.Shared 是.NET6新增的

            // 生成一个随机的过期时间，介于基础过期时间和两倍基础过期时间之间
            double sec = Random.Shared.NextDouble(baseExpireSeconds, baseExpireSeconds * 2);
            TimeSpan expiration = TimeSpan.FromSeconds(sec);

            // 设置缓存项的绝对过期时间
            entry.AbsoluteExpirationRelativeToNow = expiration;
        }

        /// <summary>
        /// 获取缓存值，如果不存在则使用提供的委托创建并缓存
        /// </summary>
        /// <typeparam name="TResult">缓存的值的类型</typeparam>
        /// <param name="cacheKey">缓存键</param>
        /// <param name="valueFactory">创建缓存值的委托</param>
        /// <param name="baseExpireSeconds">绝对过期时间（秒），默认为60秒</param>
        /// <returns>缓存的值。</returns>
        public TResult? GetOrCreate<TResult>(string cacheKey, Func<ICacheEntry, TResult?> valueFactory, int baseExpireSeconds = 60)
        {
            ValidateValueType<TResult>();
            // 因为IMemoryCache保存的是一个CacheEntry，所以null值也认为是合法的，因此返回null不会有“缓存穿透”的问题
            // 不调用系统内置的CacheExtensions.GetOrCreate，而是直接用GetOrCreate的代码，这样免得包装一次委托

            // // 如果缓存中不存在指定键的值，则执行委托创建并缓存
            if (!memoryCache.TryGetValue(cacheKey, out TResult result))
            {
                // 创建缓存项
                using ICacheEntry entry = memoryCache.CreateEntry(cacheKey);
                // 初始化缓存项的绝对过期时间
                InitCacheEntry(entry, baseExpireSeconds);
                // 执行创建缓存值的委托
                result = valueFactory(entry)!;
                // 设置缓存项的值
                entry.Value = result;
            }
            // 返回缓存的值
            return result;
        }

        /// <summary>
        /// 异步方式获取缓存值，如果不存在则使用提供的委托异步创建并缓存
        /// </summary>
        /// <typeparam name="TResult">缓存的值的类型</typeparam>
        /// <param name="cacheKey">缓存键</param>
        /// <param name="valueFactory">创建缓存值的异步委托</param>
        /// <param name="baseExpireSeconds">绝对过期时间（秒），默认为60秒</param>
        /// <returns>缓存的值</returns>
        public async Task<TResult?> GetOrCreateAsync<TResult>(string cacheKey, Func<ICacheEntry, Task<TResult?>> valueFactory, int baseExpireSeconds = 60)
        {
            ValidateValueType<TResult>();
            // 如果缓存中不存在指定键的值，则执行委托创建并缓存
            if (!memoryCache.TryGetValue(cacheKey, out TResult result))
            {
                // 创建缓存项
                using ICacheEntry entry = memoryCache.CreateEntry(cacheKey);
                // 初始化缓存项的绝对过期时间
                InitCacheEntry(entry, baseExpireSeconds);
                // 执行异步创建缓存值的委托
                result = (await valueFactory(entry))!;
                // 设置缓存项的值
                entry.Value = result;
            }
            // 返回缓存的值
            return result;
        }

        /// <summary>
        /// 移除指定缓存键的缓存项
        /// </summary>
        /// <param name="cacheKey">缓存键</param>
        public void Remove(string cacheKey)
        {
            memoryCache.Remove(cacheKey);
        }
    }
}
